﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Xml.Linq;
using System.Xml;

namespace LotroData
{
    /// <summary>
    /// Provides access to the data.lotro.com XML streams.
    /// </summary>
    public sealed class LotroXmlRequest
    {
        /// <summary>
        /// Occurs when a XML request starts.
        /// </summary>
        public event EventHandler<LotroEventArgs> RequestStarted;
        /// <summary>
        /// Called when a XML request starts.
        /// </summary>
        /// <param name="sender">The sender object.</param>
        /// <param name="e">The <see cref="LotroData.LotroEventArgs"/> instance containing the event data.</param>
        private void OnRequestStarted(object sender, LotroEventArgs e)
        {
            var temp = RequestStarted;
            if (temp != null)
                temp(sender, e);
        }

        /// <summary>
        /// Occurs when a XML request completes.
        /// </summary>
        public event EventHandler<LotroEventArgs> RequestCompleted;
        /// <summary>
        /// Called when a XML request completes.
        /// </summary>
        /// <param name="sender">The sender object.</param>
        /// <param name="e">The <see cref="LotroData.LotroEventArgs"/> instance containing the event data.</param>
        private void OnRequestCompleted(object sender, LotroEventArgs e)
        {
            var temp = RequestCompleted;
            if (temp != null)
                temp(sender, e);
        }

        private enum RequestType
        {
            charactersheet,
            guildroster
        }

        private const string BaseAddress = "http://data.lotro.com/";
        private LotroCredential Credentials;

        /// <summary>
        /// Initializes a new instance of the <see cref="LotroXmlRequest"/> class.
        /// </summary>
        /// <param name="credentials">The credentials with which to connect to data.lotro.com.</param>
        public LotroXmlRequest(LotroCredential credentials)
        {
            if (credentials == null) throw new ArgumentNullException("credentials");

            this.Credentials = credentials;
        }

        /// <summary>
        /// Gets the XML character sheet.
        /// </summary>
        /// <param name="characterName">The name of the character.</param>
        /// <param name="world">The world where the character resides.</param>
        /// <returns></returns>
        public XDocument GetCharacterSheet(string characterName, LotroWorld world)
        {
            return GetXDocument(RequestType.charactersheet, characterName, world);
        }

        /// <summary>
        /// Gets the XML guild roster.
        /// </summary>
        /// <param name="guildName">The name of the guild.</param>
        /// <param name="world">The world where the guild resides.</param>
        /// <returns></returns>
        public XDocument GetGuildRoster(string guildName, LotroWorld world)
        {
            return GetXDocument(RequestType.guildroster, guildName, world);
        }

        private XDocument GetXDocument(RequestType type, string name, LotroWorld world)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            if (world == LotroWorld.Unknown) throw new ArgumentException("Specified world cannot be Unknown", "world");

            OnRequestStarted(this, new LotroEventArgs(name, world));

            //Check cache for cached version
            var cache = LotroCache.LoadCachedXml(name, world);
            //If cached version exists, use it!
            if (cache != null)
                return cache;

            try
            {
                var request = WebRequest.Create(BuildAddress(type, name, world));

                var response = request.GetResponse();

                var reader = XmlReader.Create(response.GetResponseStream());
                reader.MoveToContent();

                //Load the XDocument
                var xDoc = XDocument.Load(reader);

                //Check for invalid XML
                var errorE = xDoc.Element("apiresponse").Element("error");
                //If it's not null, there is an error
                if (errorE != null)
                {
                    var message = errorE.Attribute("message").Value;
                    var code = errorE.Attribute("code").Value;

                    throw new LotroException(message, code);
                }

                //Save the cache file
                LotroCache.SaveCachedXml(name, world, xDoc);

                OnRequestCompleted(this, new LotroEventArgs(name, world));

                return xDoc;
            }
            catch (WebException)
            {
                throw;
            }
            catch (LotroException)
            {
                throw;
            }
        }

        private Uri BuildAddress(RequestType type, string name, LotroWorld world)
        {
            var builder = new StringBuilder();

            builder.Append(BaseAddress);

            var devName = Credentials.IsEncrypted ? Credentials.DeveloperName.Decrypt() : Credentials.DeveloperName;
            builder.Append(devName);
            builder.Append("/");

            var apiKey = Credentials.IsEncrypted ? Credentials.ApiKey.Decrypt() : Credentials.ApiKey;
            builder.Append(apiKey);
            builder.Append("/");

            builder.Append(type.ToString());
            builder.Append("/w/"); //World
            builder.Append(world.ToString());
            builder.Append("/");
            builder.Append(type.ToString()[0]); //Guild or Character (g or c)
            builder.Append("/");
            builder.Append(name);
            builder.Append("/");

            return new Uri(builder.ToString());
        }
    }
}
